Skip to content

feat: Auto-detect trace duration and render in adaptive time units (HDX-3909)#2046

Open
MikeShi42 wants to merge 9 commits intomainfrom
cursor/HDX-3909-trace-duration-unit-rendering-14bb
Open

feat: Auto-detect trace duration and render in adaptive time units (HDX-3909)#2046
MikeShi42 wants to merge 9 commits intomainfrom
cursor/HDX-3909-trace-duration-unit-rendering-14bb

Conversation

@MikeShi42
Copy link
Copy Markdown
Contributor

@MikeShi42 MikeShi42 commented Apr 2, 2026

Summary

When a chart uses a trace source with a Duration Expression, the chart now automatically defaults to adaptive time unit formatting (e.g., 120.41s, 45ms, 3µs) instead of requiring users to manually select a format. Users can still override the format through the existing display settings.

Key changes:

  1. New duration output type in NumberFormatSchema — renders values adaptively as µs, ms, s, min, or h based on magnitude, instead of the clock-style hh:mm:ss format
  2. Auto-detection via exact matchgetTraceDurationNumberFormat() checks if any chart select valueExpression exactly equals the trace source's durationExpression. Only applies for unit-preserving aggregate functions (avg, min, max, sum, quantile, any, last_value, etc.) — skips count and count_distinct
  3. useResolvedNumberFormat() hook — resolves the effective numberFormat for a chart: returns the user's explicit format if set, otherwise auto-detects duration format for trace sources
  4. UI form update — Added "Duration" option to the number format selector with input unit picker (seconds/ms/µs/ns)
  5. Display settings drawer — Shows the auto-detected format by default so users can see what's being applied
  6. Heatmap support — Updated DBHeatmapChart tick formatter to use formatDurationMs for duration-formatted values

Components updated: DBTimeChart, DBNumberChart, DBListBarChart, DBPieChart, DBTableChart, DBHeatmapChart, DBSearchHeatmapChart, DBEditTimeChartForm, ChartDisplaySettingsDrawer

How to test locally or on Vercel

  1. Create a chart from a trace source with a unit-preserving aggFn (e.g., avg/p95/p99/min/max of the Duration column)
  2. Verify the chart y-axis and tooltips now show values like 120.41s or 45ms instead of raw numbers
  3. Open the chart display settings and verify the "Duration" output format is shown as the default
  4. Change the aggFn to count or count_distinct — verify duration formatting is NOT applied
  5. Change the format to something else (e.g., "Number") and verify the override persists
  6. Switch back to "Duration" and pick different input units (seconds, ms, µs, ns) — the preview should update correctly
  7. Check that non-trace-source charts are unaffected (no auto-detection triggers)
  8. Verify the search heatmap chart for traces still shows proper duration labels
  9. Reset to defaults in the display settings drawer and verify it returns to the auto-detected duration format

References

  • Linear Issue: HDX-3909

Linear Issue: HDX-3909

Open in Web Open in Cursor 

…DX-3909)

- Add 'duration' output type to NumberFormatSchema for adaptive time unit
  rendering (e.g., '120.41s', '45ms', '3µs') instead of clock format 'hh:mm:ss'
- Add formatDurationMs() utility that adaptively formats milliseconds into
  the most appropriate unit (µs, ms, s, min, h)
- Add getTraceDurationNumberFormat() to detect when chart select expressions
  reference a trace source's durationExpression
- Add useResolvedNumberFormat() hook that auto-defaults to duration format
  when a chart uses a trace source with duration expressions and no explicit
  numberFormat is set by the user
- Update chart components (DBTimeChart, DBNumberChart, DBListBarChart,
  DBPieChart, DBTableChart) to use resolved number format
- Update NumberFormat UI form to include Duration option with input unit
  selector (seconds/ms/µs/ns)
- Update DBHeatmapChart tick formatter to support the new duration format
- Update DBSearchHeatmapChart to use duration format instead of MS_NUMBER_FORMAT
- Add comprehensive tests for formatDurationMs and getTraceDurationNumberFormat

Co-authored-by: Mike Shi <mike@hyperdx.io>
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 2, 2026

⚠️ No Changeset found

Latest commit: f867511

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Apr 9, 2026 9:34pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

E2E Test Results

All tests passed • 131 passed • 3 skipped • 1058s

Status Count
✅ Passed 131
❌ Failed 0
⚠️ Flaky 2
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

The ChartDisplaySettingsDrawer now accepts a defaultNumberFormat prop
that represents the auto-detected format (e.g., duration for trace sources).
When no explicit numberFormat is set, the drawer shows the auto-detected
format instead of the generic 'Number' default.

- Add defaultNumberFormat prop to ChartDisplaySettingsDrawer
- Compute auto-detected format in DBEditTimeChartForm and pass it down
- Reset form when defaults change via useEffect
- Update test mocks for getTraceDurationNumberFormat

Co-authored-by: Mike Shi <mike@hyperdx.io>
…unctions

Skip auto-detection for aggregate functions like count, count_distinct,
and sum whose output is not in the same unit as the input duration.
Only apply duration formatting for functions that preserve the input
unit: avg, min, max, quantile, any, last_value, heatmap, and their
combinator variants (e.g. avgIf, quantileIfState).

Added comprehensive tests for aggFn filtering.

Co-authored-by: Mike Shi <mike@hyperdx.io>
Sum of durations still produces a duration value (e.g. total time spent),
so it should inherit the duration format.

Co-authored-by: Mike Shi <mike@hyperdx.io>
@MikeShi42 MikeShi42 marked this pull request as ready for review April 2, 2026 22:02
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

PR Review

Overall this is a well-scoped, well-tested feature with good coverage of edge cases. A few items worth addressing:

  • ⚠️ isDurationPreservingAggFn regex misses plain -Merge combinatorsreplace(/If(State|Merge)?$/, '') only strips IfMerge/IfState suffixes. avgMerge, maxMerge, sumMerge, p95Merge (ClickHouse State/Merge combinator pattern without If) won't match the set and will silently disable duration detection. Add -Merge handling: aggFn.replace(/(If(State|Merge)?|Merge)$/, '') or add these to DURATION_PRESERVING_AGG_FNS explicitly.

  • ⚠️ useResolvedNumberFormat useMemo depends on full config object — If any caller passes a structurally-reconstructed config (e.g., from Jotai selector that spreads the object), every render invalidates the memo. More stable: [config.numberFormat, config.source, config.select, source] as dependencies instead of [config, source].

  • ⚠️ ChartDisplaySettingsDrawer useEffect resets unsaved changesuseEffect(() => { reset(appliedDefaults); }, [appliedDefaults, reset]) means if the user edits the form (unsaved) and then changes the aggFn on the main chart, the form silently resets. This may be intentional, but it could be surprising UX. Worth a comment explaining the intended behavior.

  • ℹ️ Renaming "Time" to "Time (clock)" — Changes visible label for an existing format option. Existing users who recognize "Time" won't see data loss, but the label change may cause minor confusion. Acceptable if intentional.

✅ Test coverage is thorough — getTraceDurationNumberFormat, formatDurationMs, formatNumber, and mock updates for all affected chart components are all covered.

Comment on lines +472 to +479
if (expr.includes(durationExpr)) {
const precision = source.durationPrecision ?? 9;
return {
output: 'duration',
factor: Math.pow(10, -precision),
};
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches Duration/1e6 and results in the wrong factor being applied.

it('detects duration ms expression reference without parentheses', () => {
    const result = getTraceDurationNumberFormat(TRACE_SOURCE, [
      { valueExpression: 'Duration/1e6' },
    ]);
    expect(result).toEqual({
      output: 'duration',
      factor: 0.001,
    });
  });

There are also other cases, like when the expression has spaces in it Duration / 1e6, (Duration) / 1e6 that don't work as expected - real parsing would be very nice here.

For this fallback case, maybe the condition should just be exact match to avoid over-matching?

Suggested change
if (expr.includes(durationExpr)) {
const precision = source.durationPrecision ?? 9;
return {
output: 'duration',
factor: Math.pow(10, -precision),
};
}
}
if (expr === durationExpr) {
const precision = source.durationPrecision ?? 9;
return {
output: 'duration',
factor: Math.pow(10, -precision),
};
}
}

'percent',
'byte', // legacy, treated as data/bytes_iec
'time',
'duration',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we update the OpenAPI Specs as well?

@github-actions github-actions bot added the review/tier-4 Critical — deep review + domain expert sign-off label Apr 6, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Standard feature/fix — introduces new logic or modifies core functionality

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Files changed: 22
  • Lines changed: 281 (+ 316 in test files, excluded from tier calculation)
  • Branch: cursor/HDX-3909-trace-duration-unit-rendering-14bb
  • Author: MikeShi42

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions github-actions bot added review/tier-3 Standard — full human review required and removed review/tier-4 Critical — deep review + domain expert sign-off labels Apr 6, 2026
The raw duration fallback (e.g. 'Duration') now requires an exact match
instead of substring includes, preventing 'Duration/1e6' from incorrectly
matching with the raw nanosecond factor.

Division expressions like 'Duration/1e6' are now matched with a regex
that handles whitespace and parentheses variants:
  (Duration)/1e6, Duration/1e6, Duration / 1e6, (Duration) / 1e6

Wrapped expressions like 'avg(Duration)' or 'round(Duration / 1e6, 2)'
correctly do not match any pattern.

Co-authored-by: Mike Shi <mike@hyperdx.io>
Remove regex-based division pattern matching. The duration expression
can be arbitrary SQL, so substring or regex matching is fragile and
error-prone. Now only applies duration formatting when the select
valueExpression exactly equals the source's durationExpression.

Co-authored-by: Mike Shi <mike@hyperdx.io>
pulpdrew
pulpdrew previously approved these changes Apr 7, 2026
@MikeShi42
Copy link
Copy Markdown
Contributor Author

@claude can you resolve merge conflicts with main? DBEditTimeChartForm was refactored into packages/app/src/components/DBEditTimeChartForm folder, so we just need to move some of our changes into the new refactored components.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Claude encountered an error —— View job


Resolving merge conflicts with main

  • Gather context: understand current branch state and conflicts
  • Explore refactored DBEditTimeChartForm folder structure in main
  • Identify changes from this PR that need to be moved into the new structure
  • Apply changes to the refactored components
  • Run lint to verify no issues
  • Commit and push resolved changes

DBEditTimeChartForm.tsx was refactored into a directory on main (PR #2026).
Applied our changes (getTraceDurationNumberFormat import, autoDetectedNumberFormat
computation, defaultNumberFormat prop on ChartDisplaySettingsDrawer) to the
new EditTimeChartForm.tsx file. Deleted the old monolithic file.

Co-authored-by: Mike Shi <mike@hyperdx.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants